/*
 Copyright (C) 2025 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#include "mdl/EntityDefinitionUtils.h"

#include "mdl/EntityProperties.h"

namespace tb::mdl
{
namespace
{

template <typename ValueType>
auto getPropertyDefinitionsWithType(const EntityDefinition& entityDefinition)
{
  return entityDefinition.propertyDefinitions
         | std::views::filter([](const auto& propertyDefinition) {
             return std::holds_alternative<ValueType>(propertyDefinition.valueType);
           });
}

auto getAllPropertyDefinitions(auto&& entityDefinitions)
{
  return entityDefinitions | std::views::transform([](auto&& entityDefinition) -> auto&& {
           return entityDefinition.propertyDefinitions;
         })
         | std::views::join;
}

bool hasAnyEntityLinkPropertyDefinition(
  const std::vector<EntityDefinition>& entityDefinitions)
{
  return std::ranges::any_of(
    getAllPropertyDefinitions(entityDefinitions), [](const auto& propertyDefinition) {
      return std::holds_alternative<PropertyValueTypes::LinkTarget>(
               propertyDefinition.valueType)
             || std::holds_alternative<PropertyValueTypes::LinkSource>(
               propertyDefinition.valueType);
    });
}

void addOrSetDefaultEntityLinkProperties(EntityDefinition& entityDefinition)
{
  static const auto keysAndValueTypes =
    std::vector<std::tuple<std::string, PropertyValueType, std::string>>{
      {EntityPropertyKeys::Target,
       PropertyValueTypes::LinkSource{},
       "name of entity to trigger"},
      {EntityPropertyKeys::Killtarget,
       PropertyValueTypes::LinkSource{},
       "name of entity to kill"},
      {EntityPropertyKeys::Targetname,
       PropertyValueTypes::LinkTarget{},
       "target name for linking"}};

  for (const auto& [key, valueType, description] : keysAndValueTypes)
  {
    if (auto* propertyDefinition = getPropertyDefinition(entityDefinition, key))
    {
      propertyDefinition->valueType = valueType;
    }
    else
    {
      entityDefinition.propertyDefinitions.push_back(
        PropertyDefinition{key, valueType, description, "generated by TrenchBroom"});
    }
  }
}

void addOrConvertOriginProperties(EntityDefinition& entityDefinition)
{
  const auto isOriginProperty = [](const auto& propertyDefinition) {
    return propertyDefinition.key == EntityPropertyKeys::Origin;
  };

  if (const auto iOriginDefinition =
        std::ranges::find_if(entityDefinition.propertyDefinitions, isOriginProperty);
      iOriginDefinition != entityDefinition.propertyDefinitions.end())
  {
    std::visit(
      kdl::overload(
        [&](PropertyValueTypes::String& stringValueType) {
          iOriginDefinition->valueType =
            PropertyValueTypes::Origin{std::move(stringValueType.defaultValue)};
        },
        [](const auto&) {}),
      iOriginDefinition->valueType);
  }
  else
  {
    entityDefinition.propertyDefinitions.push_back(PropertyDefinition{
      EntityPropertyKeys::Origin,
      PropertyValueTypes::Origin{},
      {"point entity origin"},
      {"generated by TrenchBroom"}});
  }
}

} // namespace

std::vector<const PropertyDefinition*> getLinkSourcePropertyDefinitions(
  const EntityDefinition* entityDefinition)
{
  return entityDefinition
           ? getPropertyDefinitionsWithType<PropertyValueTypes::LinkSource>(
               *entityDefinition)
               | std::views::transform(
                 [](const auto& propertyDefinition) { return &propertyDefinition; })
               | kdl::ranges::to<std::vector>()
           : std::vector<const PropertyDefinition*>{};
}

std::vector<const PropertyDefinition*> getLinkTargetPropertyDefinitions(
  const EntityDefinition* entityDefinition)
{
  return entityDefinition
           ? getPropertyDefinitionsWithType<PropertyValueTypes::LinkTarget>(
               *entityDefinition)
               | std::views::transform(
                 [](const auto& propertyDefinition) { return &propertyDefinition; })
               | kdl::ranges::to<std::vector>()
           : std::vector<const PropertyDefinition*>{};
}

bool isLinkSourceProperty(
  const EntityDefinition* entityDefinition, const std::string& key)
{
  return entityDefinition
         && std::ranges::any_of(
           getPropertyDefinitionsWithType<PropertyValueTypes::LinkSource>(
             *entityDefinition),
           [&](const auto& propertyDefinition) { return propertyDefinition.key == key; });
}

bool isLinkTargetProperty(
  const EntityDefinition* entityDefinition, const std::string& key)
{
  return entityDefinition
         && std::ranges::any_of(
           getPropertyDefinitionsWithType<PropertyValueTypes::LinkTarget>(
             *entityDefinition),
           [&](const auto& propertyDefinition) { return propertyDefinition.key == key; });
}

void addOrSetDefaultEntityLinkProperties(std::vector<EntityDefinition>& entityDefinitions)
{
  if (!hasAnyEntityLinkPropertyDefinition(entityDefinitions))
  {
    for (auto& entityDefinition : entityDefinitions)
    {
      addOrSetDefaultEntityLinkProperties(entityDefinition);
    }
  }
}

void addOrConvertOriginProperties(std::vector<EntityDefinition>& entityDefinitions)
{
  const auto isPointEntity = [](const auto& entityDefinition) {
    return entityDefinition.pointEntityDefinition != std::nullopt;
  };

  for (auto& entityDefinition : entityDefinitions | std::views::filter(isPointEntity))
  {
    addOrConvertOriginProperties(entityDefinition);
  }
}

} // namespace tb::mdl
